﻿' Copyright © Microsoft Corporation.  All Rights Reserved.
' This code released under the terms of the 
' Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
' <copyright file="IsoUri.vb" company="Microsoft">
'     Copyright © Microsoft Corporation. All rights reserved.
' </copyright>
' <summary>Isolated storage aware Uri</summary>
' <author>Microsoft Expression Encoder Team</author>

Imports Microsoft.VisualBasic
Imports System
Imports System.Net
Imports System.Windows
Imports System.IO
Imports System.IO.IsolatedStorage
Imports System.Diagnostics
Imports System.Threading
Imports System.Globalization
Imports System.Text
Imports System.Security.Cryptography
Imports System.Windows.Browser

Namespace ExpressionMediaPlayer
	''' <summary>
	''' event for download progress of this item.
	''' </summary>
	Friend Class UriDownloadProgressRoutedEventArgs
		Inherits RoutedEventArgs
		Friend Sub New(ByVal progress As Double)
			Me.Progress = progress
		End Sub
		Private privateProgress As Double
		Friend Property Progress() As Double
			Get
				Return privateProgress
			End Get
			Set(ByVal value As Double)
				privateProgress = value
			End Set
		End Property
	End Class
	''' <summary>
	''' extended Uri to support isolated offline storage. 
	''' Gives the ability to refer to IsoUri for online and offline cases.
	''' </summary>
	Friend Class IsoUri
		Inherits Uri
		Implements IDisposable
		''' <summary>
		''' mutex used when downloading file
		''' </summary>
		Private _mutexDownloadCompleted As New AutoResetEvent(False)
		''' <summary>
		''' mutex used when determining file size
		''' </summary>
		Private _mutexFileSizeCalcCompleted As New AutoResetEvent(False)
		''' <summary>
		''' progress of download
		''' </summary>
		Friend Event UriDownloadProgressChanged As EventHandler(Of UriDownloadProgressRoutedEventArgs)
		''' <summary>
		''' file size of item pointed to by this URI
		''' </summary>
		Private _fileSizeFromDownload? As Long = Nothing
		''' <summary>
		''' create empty IsoUri
		''' </summary>        
		Friend Sub New()
			MyBase.New("")
		End Sub

		''' <summary>
		''' create IsoUri form string
		''' </summary>
		''' <param name="address">location (relative or absolute)</param>
		Friend Sub New(ByVal address As String)
			MyBase.New(address, UriKind.RelativeOrAbsolute)
		End Sub

		''' <summary>
		''' create IsoUri given a seed URI
		''' </summary>
		''' <param name="uri"></param>
		Friend Sub New(ByVal uri As Uri)
			MyBase.New(uri.ToString(), UriKind.RelativeOrAbsolute)
		End Sub
		''' <summary>
		''' cleanup
		''' </summary>
		Public Sub Dispose() Implements IDisposable.Dispose
			Dispose(True)
			GC.SuppressFinalize(Me)
		End Sub
		''' <summary>
		''' clean up
		''' </summary>
		Protected Overridable Sub Dispose(ByVal disposing As Boolean)
			If disposing Then
				_mutexDownloadCompleted.Close()
				_mutexFileSizeCalcCompleted.Close()
			End If
		End Sub
		''' <summary>
		''' reverse a string
		''' </summary>
		''' <param name="strIn"></param>
		''' <returns></returns>
		Private Shared Function Reverse(ByVal strIn As String) As String
			Dim rgchStrInReversed() As Char = strIn.ToCharArray()
			Array.Reverse(rgchStrInReversed)
			Return (New String(rgchStrInReversed))
		End Function
		''' <summary>
		''' return the Iso stream name for this Uri
		''' </summary>              
		Friend ReadOnly Property StreamName() As String
			Get
				Dim strUrl As String = Me.ToString()
				Dim hash As New SHA1Managed()
				Dim rgb() As Byte = hash.ComputeHash(Encoding.Unicode.GetBytes(strUrl))
				Dim sbEncode As New StringBuilder()
				sbEncode.AppendFormat(CultureInfo.InvariantCulture, "{0:X8}{1:X8}-", strUrl.GetHashCode(), Reverse(strUrl).GetHashCode())
				For Each by As Byte In rgb
					sbEncode.AppendFormat(CultureInfo.InvariantCulture, "{0:X2}", by)
				Next by
				Return sbEncode.ToString()
			End Get
		End Property
		''' <summary>
		''' return an Iso stream from this Uri
		''' </summary>
		Friend ReadOnly Property Stream() As Stream
			Get
				Dim iso As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()
				Return iso.OpenFile(StreamName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
			End Get
		End Property
		''' <summary>
		''' local copy of file exists in isolated storage for this Uri
		''' </summary>
		Friend ReadOnly Property IsoFileExists() As Boolean
			Get
				Try
					Dim iso As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()
					Return iso.FileExists(StreamName)
				Catch ise As IsolatedStorageException
					Debug.WriteLine("IsolatedStorageException testing for file exists! " & ise.ToString())
					Return False
				End Try
			End Get
		End Property

		Private privateDownloadErrorMessage As String
		Friend Property DownloadErrorMessage() As String
			Get
				Return privateDownloadErrorMessage
			End Get
			Set(ByVal value As String)
				privateDownloadErrorMessage = value
			End Set
		End Property
		Private privateDownloadSucceeded As Boolean
		Friend Property DownloadSucceeded() As Boolean
			Get
				Return privateDownloadSucceeded
			End Get
			Set(ByVal value As Boolean)
				privateDownloadSucceeded = value
			End Set
		End Property
		''' <summary>
		''' download the file referred to by this Uri from the cloud to Iso storage
		''' note: call blocks until download completed
		''' </summary>        
		Friend Sub Download()
#If DEBUG Then
			Debug.WriteLine("IsoUri.Download:" & Me.ToString())
#End If
			DownloadErrorMessage = Nothing
			DownloadSucceeded = False
			RaiseEvent UriDownloadProgressChanged(Me, New UriDownloadProgressRoutedEventArgs(0.0))
			_mutexDownloadCompleted.Reset()
			Dim webClient As New WebClient()
			AddHandler webClient.DownloadProgressChanged, AddressOf webClient_DownloadProgressChanged
			AddHandler webClient.OpenReadCompleted, AddressOf webClient_OpenReadCompleted
			webClient.OpenReadAsync(Me)
			_mutexDownloadCompleted.WaitOne()
			If DownloadSucceeded Then
				Debug.Assert(IsoFileExists)
			End If
		End Sub
		''' <summary>
		''' keep updated. the web download accounts for 1/2 the progress, the local copy to iso store for the other 1/2. 
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		Private Sub webClient_DownloadProgressChanged(ByVal sender As Object, ByVal e As DownloadProgressChangedEventArgs)
			RaiseEvent UriDownloadProgressChanged(Me, New UriDownloadProgressRoutedEventArgs(e.ProgressPercentage / 200.0))
		End Sub

		Private Const maxDownloadBufferSize As Integer = 1 << 18 ' 256KB buffer size

		''' <summary>
		''' callback when remote file opened for read. will copy file to isolated storage
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="args"></param>
		Private Sub webClient_OpenReadCompleted(ByVal sender As Object, ByVal args As OpenReadCompletedEventArgs)
#If DEBUG Then
			Debug.WriteLine("IsoUri.webClient_OpenReadCompleted:" & Me.ToString())
#End If
			' Check for error conditions
			If args.Cancelled OrElse Nothing IsNot args.Error Then
				DownloadErrorMessage = args.Error.Message
				DownloadSucceeded = False
				_mutexDownloadCompleted.Set()
				Return
			End If

			Dim isoFile As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()
			Dim _outputStream As IsolatedStorageFileStream = Nothing
			Dim inputStream As Stream = args.Result

			Me._fileSizeFromDownload = inputStream.Length

			Try
				_outputStream = isoFile.CreateFile(StreamName)
                Debug.WriteLine("ISO: file adding " & StreamName & " - " & Me.ToString)
				Dim bufferSize As Long = Math.Min(maxDownloadBufferSize, inputStream.Length)
                Dim buf(CInt(bufferSize) - 1) As Byte
				Dim length As Long = inputStream.Length
				Dim read As Integer = 0, totalread As Integer = 0
				read = inputStream.Read(buf, 0, buf.Length)
				Do While read > 0
					_outputStream.Write(buf, 0, read)
					totalread += read

					RaiseEvent UriDownloadProgressChanged(Me, New UriDownloadProgressRoutedEventArgs(0.5 + 0.5 * (CDbl(totalread) / CDbl(length))))
					read = inputStream.Read(buf, 0, buf.Length)
				Loop
				DownloadErrorMessage = String.Empty
				DownloadSucceeded = True

			Catch ex As IsolatedStorageException
                Debug.WriteLine("ISO: failed to copy file successfully into storage " & StreamName & " - " & Me.ToString & ": " & ex.Message)
				DownloadErrorMessage = ex.Message
				DownloadSucceeded = False
			Catch e As Exception
                Debug.WriteLine("ISOURI: Exception during download:" & StreamName & " - " & Me.ToString & ": " & e.Message)
				DownloadErrorMessage = e.Message
				DownloadSucceeded = False
			Finally
				If _outputStream IsNot Nothing Then
					_outputStream.Close()
				End If
				If inputStream IsNot Nothing Then
					inputStream.Close()
				End If
				_mutexDownloadCompleted.Set()
			End Try
		End Sub

#If _NO_LONGER_USED Then
		''' <summary>
		''' determine the file size of the file pointed to by this URI, unfortunately we need
		''' to fetch it. Note: will block, needs to be called off-UI-thread
		''' </summary>
		Friend ReadOnly Property FileSizeOnceDownloaded() As Long?
			Get
				Return _fileSizeFromDownload
			End Get
		End Property


		''' <summary>
		''' remove this file from Iso storage
		''' </summary>
		Friend Sub DeleteFromIsoStorage()
			Dim iso As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()
			iso.DeleteFile(StreamName)
		End Sub
#End If

		''' <summary>
		''' clear all isostorage files associated with this application
		''' </summary>
		Friend Shared Sub ClearIsoStorage()
			IsolatedStorageFile.GetUserStoreForApplication().Remove()
		End Sub
		''' <summary>
		''' gets a fully qualified version of this URI if possible
		''' </summary>
		Friend ReadOnly Property FullyQualifiedUri() As Uri
			Get
				Try
					If (Not Me.IsAbsoluteUri) AndAlso HtmlPage.IsEnabled AndAlso HtmlPage.Document IsNot Nothing AndAlso HtmlPage.Document.DocumentUri IsNot Nothing AndAlso (Not String.IsNullOrEmpty(Me.ToString())) Then
						Dim uri As Uri = HtmlPage.Document.DocumentUri
						Dim urib As New UriBuilder(uri)
						urib.Path = System.Uri.UnescapeDataString(uri.AbsolutePath.Substring(0, uri.AbsolutePath.LastIndexOf("/", StringComparison.Ordinal) + 1) & Me.OriginalString)
						Return urib.Uri
					End If
				Catch e1 As InvalidOperationException
					' can occur offline. no HTML bridge
				End Try

				Return Me
			End Get
		End Property
	End Class
End Namespace
